
/********************************** LICENSE ************************************
 *
 * Copyright 2011 Alessandro Carrega
 * email: alessandro.carrega@unige.it
 *
 * This file is part of web_lite.
 *
 * web_lite is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * web_lite is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with web_lite.  If not, see <http://www.gnu.org/licenses/>.
 *
 ********************************* LICENSE ************************************/

//====================================================== file = weblite.c =====
//=  A super light-weight secure HTTP server                                  =
//=   - Uses threads to allow for parallel connections                        =
//=============================================================================
//=  Notes:                                                                   =
//=    1) Compiles for Windows (using Winsock and Windows threads) and Unix   =
//=       (using BSD sockets and POSIX threads)                               =
//=    2) Serves HTML, text, and GIF only.                                    =
//=    3) Serves files only from directory that weblite is running in.  This  =
//=       makes weblite "secure".                                             =
//=    4) Sometimes the browser drops a connection when doing a refresh.      =
//=       This is handled by checking the recv() return code in the           =
//=       function that handles GETs.  This is only seen when using           =
//=       Explorer.                                                           =
//=    5) The 404 HTML message does not always display in Explorer.           =
//=    6) Ignore the compile-time warnings regarding unreachable code         =
//=       in main().                                                          =
//=---------------------------------------------------------------------------=
//=  Execution notes:                                                         =
//=   1) Execute this program in the directory which will be the root for     =
//=      all file references (i.e., the directory that is considered at       =
//=      "public.html").                                                      =
//=   2) Open a Web browser and surf http://xxx.xxx.xxx.xxx:8080/yyy where    =
//=      xxx.xxx.xxx.xxx is the IP address or hostname of the machine that    =
//=      weblite is executing on and yyy is the requested object.             =
//=   3) The only non-error output (to stdout) from weblite is a message      =
//=      with the name of the file currently being sent.                      =
//=---------------------------------------------------------------------------=
//=  Build: Windows: bcc32 -WM weblite.c, cl /MT weblite.c wsock32.lib        =
//=         Unix: gcc weblite.c -lpthread -lnsl -o weblite                    =
//=---------------------------------------------------------------------------=
//=  Execute: weblite                                                         =
//=---------------------------------------------------------------------------=
//=  History:  KJC (10/08/02) - Genesis                                       =
//=            KJC (09/11/05) - Fixed "GET \./../" security hole              =
//=            KJC (01/29/06) - Add BSD as conditional compile (thanks to     =
//=                             James Poag for POSIX threads howto)           =
//=            KJC (12/06/06) - Fixed pthread call (thanks to Nicholas        =
//=                             Paltzer for finding and fixing the problem)   =
//=            KJC (09/09/09) - Changed port to 8080 and fixed gcc build      =
//=============================================================================
//#define  WIN                // WIN for Winsock and BSD for BSD sockets
#define  BSD                // WIN for Winsock and BSD for BSD sockets

//----- Include files ---------------------------------------------------------
#include <stdio.h>          // Needed for printf()
#include <stdlib.h>         // Needed for exit()
#include <string.h>         // Needed for memcpy() and strcpy()
#include <fcntl.h>          // Needed for file i/o stuff
#ifdef WIN
#include <process.h>      // Needed for _beginthread() and _endthread()
#include <stddef.h>       // Needed for _threadid
#include <windows.h>      // Needed for all Winsock stuff
#include <sys\stat.h>     // Needed for file i/o constants
#include <io.h>           // Needed for file i/o stuff
#endif
#ifdef BSD
#include <unistd.h>
#include <pthread.h>      // Needed for pthread_create() and pthread_exit()
#include <sys/stat.h>     // Needed for file i/o constants
#include <sys/types.h>    // Needed for sockets stuff
#include <netinet/in.h>   // Needed for sockets stuff
#include <sys/socket.h>   // Needed for sockets stuff
#include <arpa/inet.h>    // Needed for sockets stuff
#include <netdb.h>        // Needed for sockets stuff
#endif

//----- HTTP response messages ----------------------------------------------
#define OK_IMAGE  "HTTP/1.0 200 OK\r\nContent-Type:image/gif\r\n\r\n"
#define OK_TEXT   "HTTP/1.0 200 OK\r\nContent-Type:text/html\r\n\r\n"
#define NOTOK_404 "HTTP/1.0 404 Not Found\r\nContent-Type:text/html\r\n\r\n"
#define MESS_404  "<html><body><h1>FILE NOT FOUND</h1></body></html>"

//----- Defines -------------------------------------------------------------
#define  PORT_NUM            8080     // Port number for Web server
#define  BUF_SIZE            4096     // Buffer size (big enough for a GET)

//----- Function prototypes -------------------------------------------------
#ifdef WIN
void handle_get(void *in_arg);      // Windows thread function to handle GET
#endif
#ifdef BSD
void *handle_get(void *in_arg);     // POSIX thread function to handle GET
#endif

//===== Main program ========================================================
int main(void)
{
#ifdef WIN
	WORD wVersionRequested = MAKEWORD(1,1);    // Stuff for WSA functions
	WSADATA wsaData;                           // Stuff for WSA functions
#endif
	int                  server_s;             // Server socket descriptor
	struct sockaddr_in   server_addr;          // Server Internet address
	int                  client_s;             // Client socket descriptor
	struct sockaddr_in   client_addr;          // Client Internet address
	struct in_addr       client_ip_addr;       // Client IP address
#ifdef WIN
	int                  addr_len;             // Internet address length
#endif
#ifdef BSD
	socklen_t            addr_len;             // Internet address length
	pthread_t            thread_id;            // Thread ID
#endif
	int                  retcode;              // Return code

#ifdef WIN
	// This stuff initializes winsock
	WSAStartup(wVersionRequested, &wsaData);
#endif

	// Create a socket, fill-in address information, and then bind it
	server_s = socket(AF_INET, SOCK_STREAM, 0);
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT_NUM);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	retcode = bind(server_s, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (retcode < 0) {
		printf("*** ERROR - bind() failed \n");
		exit(-1);
	}

	// Set-up the listen
	listen(server_s, 100);

	// Main loop to accept connections and then spin-off thread to handle the GET
	printf(">>> weblite is running on port %d <<< \n", PORT_NUM);
	while(1) {
		addr_len = sizeof(client_addr);
		client_s = accept(server_s, (struct sockaddr *)&client_addr, &addr_len);
		if (client_s == -1) {
			printf("ERROR - Unable to create a socket \n");
			exit(1);
		}

#ifdef WIN
		if (_beginthread(handle_get, 4096, (void *)client_s) < 0)
#endif
#ifdef BSD
			if (pthread_create(&thread_id, NULL, handle_get, (void *)client_s) != 0)
#endif
			{
				printf("ERROR - Unable to create a thread to handle the GET \n");
				exit(1);
			}
	}

	return(0);
}

//===========================================================================
//=  This is is the thread function to handle the GET                       =
//===========================================================================
#ifdef WIN
void handle_get(void *in_arg)
#endif
#ifdef BSD
void *handle_get(void *in_arg)
#endif
{
	int            client_s;             // Client socket descriptor
	char           in_buf[BUF_SIZE];     // Input buffer for GET request
	char           out_buf[BUF_SIZE];    // Output buffer for HTML response
	int            fh;                   // File handle
	int            buf_len;              // Buffer length for file reads
	char           command[BUF_SIZE];    // Command buffer
	char           file_name[BUF_SIZE];  // File name buffer
	int            retcode;              // Return code

	// Set client_s to in_arg
	client_s = (int) in_arg;

	// Receive the (presumed) GET request from the Web browser
	retcode = recv(client_s, in_buf, BUF_SIZE, 0);

	// If the recv() return code is bad then bail-out (see note #3)
	if (retcode <= 0) {
		printf("ERROR - Receive failed --- probably due to dropped connection \n");
#ifdef WIN
		closesocket(client_s);
		_endthread();
#endif
#ifdef BSD
		close(client_s);
		pthread_exit(NULL);
#endif
	}

	// Parse out the command from the (presumed) GET request and filename
	sscanf(in_buf, "%s %s \n", command, file_name);

	// Check if command really is a GET, if not then bail-out
	if (strcmp(command, "GET") != 0) {
		printf("ERROR - Not a GET --- received command = '%s' \n", command);
#ifdef WIN
		closesocket(client_s);
		_endthread();
#endif
#ifdef BSD
		close(client_s);
		pthread_exit(NULL);
#endif
	}

	// It must be a GET... open the requested file
	//  - Start at 2nd char to get rid of leading "\"
#ifdef WIN
	fh = open(&file_name[1], O_RDONLY | O_BINARY, S_IREAD | S_IWRITE);
#endif
#ifdef BSD
	fh = open(&file_name[1], O_RDONLY, S_IREAD | S_IWRITE);
#endif

	// If file does not exist, then return a 404 and bail-out
	if (fh == -1) {
		printf("File '%s' not found --- sending an HTTP 404 \n", &file_name[1]);
		strcpy(out_buf, NOTOK_404);
		send(client_s, out_buf, strlen(out_buf), 0);
		strcpy(out_buf, MESS_404);
		send(client_s, out_buf, strlen(out_buf), 0);
#ifdef WIN
		closesocket(client_s);
		_endthread();
#endif
#ifdef BSD
		close(client_s);
		pthread_exit(NULL);
#endif
	}

	// Check that filename does not start with a ".", "/", "\", or have a ":" in
	// the second position indicating a disk identifier (e.g., "c:").
	//  - This is a security check to prevent grabbing any file on the server
	if ((file_name[1] == '.') || (file_name[1] == '/') || (file_name[1] == '\\') || (file_name[2] == ':')) {
		printf("SECURITY VIOLATION --- trying to read '%s' \n", &file_name[1]);
		close(fh);
#ifdef WIN
		closesocket(client_s);
		_endthread();
#endif
#ifdef BSD
		close(client_s);
		pthread_exit(NULL);
#endif
	}

	// Generate and send the response
	printf("Sending file '%s' \n", &file_name[1]);
	if (strstr(file_name, ".gif") != NULL)
		strcpy(out_buf, OK_IMAGE);
	else
		strcpy(out_buf, OK_TEXT);
	send(client_s, out_buf, strlen(out_buf), 0);
	while(1) {
		buf_len = read(fh, out_buf, BUF_SIZE);
		if (buf_len == 0) break;
		send(client_s, out_buf, buf_len, 0);
	}

	// Close the file, close the client socket, and end the thread
	close(fh);
#ifdef WIN
	closesocket(client_s);
	_endthread();
#endif
#ifdef BSD
	close(client_s);
	pthread_exit(NULL);
#endif
}
